/*
 * Created on 12-Jan-2005
 * Created by Paul Gardner
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package com.aelitis.azureus.core.dht.impl;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Properties;

import org.gudy.azureus2.core3.util.AERunStateHandler;
import org.gudy.azureus2.core3.util.Debug;

import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.DHTListener;
import com.aelitis.azureus.core.dht.DHTLogger;
import com.aelitis.azureus.core.dht.DHTOperationListener;
import com.aelitis.azureus.core.dht.DHTStorageAdapter;
import com.aelitis.azureus.core.dht.control.DHTControl;
import com.aelitis.azureus.core.dht.control.DHTControlAdapter;
import com.aelitis.azureus.core.dht.control.DHTControlFactory;
import com.aelitis.azureus.core.dht.db.DHTDB;
import com.aelitis.azureus.core.dht.nat.DHTNATPuncher;
import com.aelitis.azureus.core.dht.nat.DHTNATPuncherAdapter;
import com.aelitis.azureus.core.dht.nat.DHTNATPuncherFactory;
import com.aelitis.azureus.core.dht.netcoords.DHTNetworkPositionManager;
import com.aelitis.azureus.core.dht.router.DHTRouter;
import com.aelitis.azureus.core.dht.speed.DHTSpeedTester;
import com.aelitis.azureus.core.dht.speed.DHTSpeedTesterFactory;
import com.aelitis.azureus.core.dht.transport.DHTTransport;
import com.aelitis.azureus.core.dht.transport.DHTTransportContact;
import com.aelitis.azureus.core.dht.transport.DHTTransportValue;
import com.aelitis.azureus.core.util.CopyOnWriteList;

/**
 * @author parg
 * 
 */

public class DHTImpl implements DHT, AERunStateHandler.RunStateChangeListener {
    private DHTStorageAdapter storage_adapter;
    private DHTNATPuncherAdapter nat_adapter;
    private DHTControl control;
    private DHTNATPuncher nat_puncher;
    private DHTSpeedTester speed_tester;
    private Properties properties;
    private DHTLogger logger;

    private CopyOnWriteList<DHTListener> listeners = new CopyOnWriteList<DHTListener>();

    private boolean runstate_startup = true;
    private boolean sleeping = false;

    public DHTImpl(DHTTransport _transport, Properties _properties, DHTStorageAdapter _storage_adapter, DHTNATPuncherAdapter _nat_adapter,
            DHTLogger _logger) {
        properties = _properties;
        storage_adapter = _storage_adapter;
        nat_adapter = _nat_adapter;
        logger = _logger;

        DHTNetworkPositionManager.initialise(storage_adapter);

        DHTLog.setLogger(logger);

        int K = getProp(PR_CONTACTS_PER_NODE, DHTControl.K_DEFAULT);
        int B = getProp(PR_NODE_SPLIT_FACTOR, DHTControl.B_DEFAULT);
        int max_r = getProp(PR_MAX_REPLACEMENTS_PER_NODE, DHTControl.MAX_REP_PER_NODE_DEFAULT);
        int s_conc = getProp(PR_SEARCH_CONCURRENCY, DHTControl.SEARCH_CONCURRENCY_DEFAULT);
        int l_conc = getProp(PR_LOOKUP_CONCURRENCY, DHTControl.LOOKUP_CONCURRENCY_DEFAULT);
        int o_rep = getProp(PR_ORIGINAL_REPUBLISH_INTERVAL, DHTControl.ORIGINAL_REPUBLISH_INTERVAL_DEFAULT);
        int c_rep = getProp(PR_CACHE_REPUBLISH_INTERVAL, DHTControl.CACHE_REPUBLISH_INTERVAL_DEFAULT);
        int c_n = getProp(PR_CACHE_AT_CLOSEST_N, DHTControl.CACHE_AT_CLOSEST_N_DEFAULT);
        boolean e_c = getProp(PR_ENCODE_KEYS, DHTControl.ENCODE_KEYS_DEFAULT) == 1;
        boolean r_p = getProp(PR_ENABLE_RANDOM_LOOKUP, DHTControl.ENABLE_RANDOM_DEFAULT) == 1;

        control = DHTControlFactory.create(new DHTControlAdapter() {
            public DHTStorageAdapter getStorageAdapter() {
                return (storage_adapter);
            }

            public boolean isDiversified(byte[] key) {
                if (storage_adapter == null) {

                    return (false);
                }

                return (storage_adapter.isDiversified(key));
            }

            public byte[][] diversify(String description, DHTTransportContact cause, boolean put_operation, boolean existing, byte[] key,
                    byte type, boolean exhaustive, int max_depth) {
                boolean valid;

                if (existing) {

                    valid = type == DHT.DT_FREQUENCY || type == DHT.DT_SIZE || type == DHT.DT_NONE;
                } else {

                    valid = type == DHT.DT_FREQUENCY || type == DHT.DT_SIZE;
                }

                if (storage_adapter != null && valid) {

                    if (existing) {

                        return (storage_adapter.getExistingDiversification(key, put_operation, exhaustive, max_depth));

                    } else {

                        return (storage_adapter.createNewDiversification(description, cause, key, put_operation, type, exhaustive, max_depth));
                    }
                } else {

                    if (!valid) {

                        Debug.out("Invalid diversification received: type = " + type);
                    }

                    if (existing) {

                        return (new byte[][] { key });

                    } else {

                        return (new byte[0][]);
                    }
                }
            }
        }, _transport, K, B, max_r, s_conc, l_conc, o_rep, c_rep, c_n, e_c, r_p, logger);

        if (nat_adapter != null) {

            nat_puncher = DHTNATPuncherFactory.create(nat_adapter, this);
        }

        AERunStateHandler.addListener(this, true);
    }

    public DHTImpl(DHTTransport _transport, DHTRouter _router, DHTDB _database, Properties _properties, DHTStorageAdapter _storage_adapter,
            DHTLogger _logger) {
        properties = _properties;
        storage_adapter = _storage_adapter;
        logger = _logger;

        DHTNetworkPositionManager.initialise(storage_adapter);

        DHTLog.setLogger(logger);

        int K = getProp(PR_CONTACTS_PER_NODE, DHTControl.K_DEFAULT);
        int B = getProp(PR_NODE_SPLIT_FACTOR, DHTControl.B_DEFAULT);
        int max_r = getProp(PR_MAX_REPLACEMENTS_PER_NODE, DHTControl.MAX_REP_PER_NODE_DEFAULT);
        int s_conc = getProp(PR_SEARCH_CONCURRENCY, DHTControl.SEARCH_CONCURRENCY_DEFAULT);
        int l_conc = getProp(PR_LOOKUP_CONCURRENCY, DHTControl.LOOKUP_CONCURRENCY_DEFAULT);
        int o_rep = getProp(PR_ORIGINAL_REPUBLISH_INTERVAL, DHTControl.ORIGINAL_REPUBLISH_INTERVAL_DEFAULT);
        int c_rep = getProp(PR_CACHE_REPUBLISH_INTERVAL, DHTControl.CACHE_REPUBLISH_INTERVAL_DEFAULT);
        int c_n = getProp(PR_CACHE_AT_CLOSEST_N, DHTControl.CACHE_AT_CLOSEST_N_DEFAULT);
        boolean e_c = getProp(PR_ENCODE_KEYS, DHTControl.ENCODE_KEYS_DEFAULT) == 1;
        boolean r_p = getProp(PR_ENABLE_RANDOM_LOOKUP, DHTControl.ENABLE_RANDOM_DEFAULT) == 1;

        control = DHTControlFactory.create(new DHTControlAdapter() {
            public DHTStorageAdapter getStorageAdapter() {
                return (storage_adapter);
            }

            public boolean isDiversified(byte[] key) {
                if (storage_adapter == null) {

                    return (false);
                }

                return (storage_adapter.isDiversified(key));
            }

            public byte[][] diversify(String description, DHTTransportContact cause, boolean put_operation, boolean existing, byte[] key,
                    byte type, boolean exhaustive, int max_depth) {
                boolean valid;

                if (existing) {

                    valid = type == DHT.DT_FREQUENCY || type == DHT.DT_SIZE || type == DHT.DT_NONE;
                } else {

                    valid = type == DHT.DT_FREQUENCY || type == DHT.DT_SIZE;
                }

                if (storage_adapter != null && valid) {

                    if (existing) {

                        return (storage_adapter.getExistingDiversification(key, put_operation, exhaustive, max_depth));

                    } else {

                        return (storage_adapter.createNewDiversification(description, cause, key, put_operation, type, exhaustive, max_depth));
                    }
                } else {

                    if (!valid) {

                        Debug.out("Invalid diversification received: type = " + type);
                    }

                    if (existing) {

                        return (new byte[][] { key });

                    } else {

                        return (new byte[0][]);
                    }
                }
            }
        }, _transport, _router, _database, K, B, max_r, s_conc, l_conc, o_rep, c_rep, c_n, e_c, r_p, logger);
    }

    public void runStateChanged(long run_state) {
        try {
            boolean is_sleeping = AERunStateHandler.isDHTSleeping();

            if (sleeping != is_sleeping) {

                sleeping = is_sleeping;

                if (!runstate_startup) {

                    System.out.println("DHT sleeping=" + sleeping);
                }
            }

            control.setSleeping(sleeping);

            DHTSpeedTester old_tester = null;
            DHTSpeedTester new_tester = null;

            synchronized (this) {

                if (sleeping) {

                    if (speed_tester != null) {

                        old_tester = speed_tester;

                        speed_tester = null;
                    }
                } else {

                    new_tester = speed_tester = DHTSpeedTesterFactory.create(this);
                }
            }

            if (old_tester != null) {

                if (!runstate_startup) {

                    System.out.println("    destroying speed tester");
                }

                old_tester.destroy();
            }

            if (new_tester != null) {

                if (!runstate_startup) {

                    System.out.println("    creating speed tester");
                }

                for (DHTListener l : listeners) {

                    try {
                        l.speedTesterAvailable(new_tester);

                    } catch (Throwable e) {

                        Debug.out(e);
                    }
                }
            }
        } finally {

            runstate_startup = false;
        }
    }

    public boolean isSleeping() {
        return (sleeping);
    }

    public void setSuspended(boolean susp) {
        if (susp) {

            if (nat_puncher != null) {

                nat_puncher.setSuspended(true);
            }

            control.setSuspended(true);

        } else {

            control.setSuspended(false);

            if (nat_puncher != null) {

                nat_puncher.setSuspended(false);
            }
        }
    }

    protected int getProp(String name, int def) {
        Integer x = (Integer) properties.get(name);

        if (x == null) {

            properties.put(name, new Integer(def));

            return (def);
        }

        return (x.intValue());
    }

    public int getIntProperty(String name) {
        return (((Integer) properties.get(name)).intValue());
    }

    public boolean isDiversified(byte[] key) {
        return (control.isDiversified(key));
    }

    public void put(byte[] key, String description, byte[] value, short flags, DHTOperationListener listener) {
        control.put(key, description, value, flags, (byte) 0, DHT.REP_FACT_DEFAULT, true, listener);
    }

    public void put(byte[] key, String description, byte[] value, short flags, boolean high_priority, DHTOperationListener listener) {
        control.put(key, description, value, flags, (byte) 0, DHT.REP_FACT_DEFAULT, high_priority, listener);
    }

    public void
            put(byte[] key, String description, byte[] value, short flags, byte life_hours, boolean high_priority, DHTOperationListener listener) {
        control.put(key, description, value, flags, life_hours, DHT.REP_FACT_DEFAULT, high_priority, listener);
    }

    public void put(byte[] key, String description, byte[] value, short flags, byte life_hours, byte replication_control, boolean high_priority,
            DHTOperationListener listener) {
        control.put(key, description, value, flags, life_hours, replication_control, high_priority, listener);
    }

    public DHTTransportValue getLocalValue(byte[] key) {
        return (control.getLocalValue(key));
    }

    public void get(byte[] key, String description, short flags, int max_values, long timeout, boolean exhaustive, boolean high_priority,
            DHTOperationListener listener) {
        control.get(key, description, flags, max_values, timeout, exhaustive, high_priority, listener);
    }

    public byte[] remove(byte[] key, String description, DHTOperationListener listener) {
        return (control.remove(key, description, listener));
    }

    public byte[] remove(DHTTransportContact[] contacts, byte[] key, String description, DHTOperationListener listener) {
        return (control.remove(contacts, key, description, listener));
    }

    public DHTTransport getTransport() {
        return (control.getTransport());
    }

    public DHTRouter getRouter() {
        return (control.getRouter());
    }

    public DHTControl getControl() {
        return (control);
    }

    public DHTDB getDataBase() {
        return (control.getDataBase());
    }

    public DHTNATPuncher getNATPuncher() {
        return (nat_puncher);
    }

    public DHTSpeedTester getSpeedTester() {
        return (speed_tester);
    }

    public DHTStorageAdapter getStorageAdapter() {
        return (storage_adapter);
    }

    public void integrate(boolean full_wait) {
        control.seed(full_wait);

        if (nat_puncher != null) {

            nat_puncher.start();
        }
    }

    public void destroy() {
        if (nat_puncher != null) {

            nat_puncher.destroy();
        }

        DHTNetworkPositionManager.destroy(storage_adapter);

        AERunStateHandler.removeListener(this);

        if (control != null) {

            control.destroy();
        }

        if (speed_tester != null) {

            speed_tester.destroy();
        }
    }

    public void exportState(DataOutputStream os, int max)

    throws IOException {
        control.exportState(os, max);
    }

    public void importState(DataInputStream is)

    throws IOException {
        control.importState(is);
    }

    public void setLogging(boolean on) {
        DHTLog.setLogging(on);
    }

    public DHTLogger getLogger() {
        return (logger);
    }

    public void print(boolean full) {
        control.print(full);
    }

    public void addListener(DHTListener listener) {
        listeners.add(listener);

        DHTSpeedTester st = speed_tester;

        if (st != null) {

            listener.speedTesterAvailable(st);
        }
    }

    public void removeListener(DHTListener listener) {
        listeners.remove(listener);
    }
}
